﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.ServiceModel;
using System.Runtime.Serialization.Formatters.Binary;
using NLite.Log;
using System.ServiceModel.Channels;

namespace NLite.Domain.Wcf.Client
{
    public class ServiceDispatcherClient : BooleanDisposable, IServiceDispatcher
    {
        static readonly ILog log = LogManager.GetLogger("Wcf.Dispatcher.Client");

        class ChannelFactoryWrapper
        {
            private static readonly object Mutex = new object();
            private  Dictionary<string, FactoryInfo> factories = new Dictionary<string, FactoryInfo>(StringComparer.InvariantCultureIgnoreCase);
            ChannelFactoryWrapper()
            {
            }

            class FactoryInfo
            {
                public long Counter;
                public ChannelFactory<IRemoteDispatcherService> Factory;
            }

            public static readonly ChannelFactoryWrapper Instance = new ChannelFactoryWrapper();

            public ChannelFactory<IRemoteDispatcherService> Increase(string key, string address, System.ServiceModel.Channels.Binding binding)
            {
                FactoryInfo item;
                
                lock (Mutex)
                {
                    if (!factories.TryGetValue(key, out item))
                    {
                        item = new FactoryInfo { Factory = new ChannelFactory<IRemoteDispatcherService>(binding, new EndpointAddress(address)) };
                        factories[key] = item;
                    }
                        
                    item.Counter++;
                    return item.Factory;
                }
            }

           
        }
        
        private string address;
        private System.ServiceModel.Channels.Binding binding;
        private readonly string BindingKey;

        public ServiceDispatcherClient(string address, System.ServiceModel.Channels.Binding binding)
        {
            this.address = address;
            this.binding = binding;
            BindingKey = address.Trim() + binding.GetType().FullName;
        }

        protected override void Dispose(bool disposing)
        {
            //if (disposing)
            //    ChannelFactoryWrapper.Instance.Decrease(BindingKey);
        }


        public IServiceResponse Dispatch(IServiceRequest req)
        {

            var reqBody = new RequestBody
            {
                Data = req.Arguments,
            };

            var ctx = req.Context;
            if (ctx != null&&ctx.Count >0)
                reqBody.Context = ctx;
          
            byte[] reqBodyStream = null;
            var fmt = new BinaryFormatter();
            using (var stream = new MemoryStream())
            {
                try
                {
                    fmt.Serialize(stream, reqBody);
                    reqBodyStream = stream.ToArray();
                }
                catch (Exception ex)
                {
                    log.Error(ex.Message, ex);
                }
            }

            var wrapperReq = new RequestWrapper
            {
                ServiceName = req.ServiceName,
                OperationName = req.OperationName,
                ValidateRequest = req.ValidateRequest,
                Body = reqBodyStream,
            };

            ResponseWrapper responseWrapper = null;
            ICommunicationObject channel = null;
            Exception exception = null;
            try
            {
                var dispatcher = ChannelFactoryWrapper.Instance.Increase(BindingKey,address, binding).CreateChannel();
                channel = dispatcher as ICommunicationObject;

                responseWrapper = dispatcher.Dispatch(wrapperReq);
                channel.Close();
            }
            catch (CommunicationException ex)
            {
                exception = ex;
                if (channel != null)
                    channel.Abort();
            }
            catch (TimeoutException ex)
            {
                exception = ex;
                if (channel != null)
                    channel.Abort();
            }
            catch (Exception ex)
            {
                exception = ex;
            }

            if (responseWrapper == null)
                responseWrapper = new ResponseWrapper();

            var resp = new ServiceResponse
            {
                Success = responseWrapper.Success,
            };

            if (exception != null)
            {
                log.Error(exception.Message, exception);
                resp.AddException(exception);
            }

            ResponseBody respBody = null;
            if (responseWrapper.Body != null && responseWrapper.Body.Length > 0)
            {
                using (var stream = new MemoryStream(responseWrapper.Body))
                {
                    try
                    {
                        respBody = fmt.Deserialize(stream) as ResponseBody;
                    }
                    catch (Exception ex)
                    {
                        log.Error(ex.Message, ex);
                        resp.AddException(ex);
                    }
                }
            }

            

            if (respBody != null)
            {
                resp.Result = respBody.Data;

                if (respBody.ErrorState != null)
                    resp.ErrorState.AddRange(respBody.ErrorState);

                if (respBody.Exceptions != null)
                    foreach (var ex in respBody.Exceptions)
                        resp.AddException(ex);

                if (respBody.Context != null && respBody.Context.Count > 0)
                    Server.DispatcherService.SetRequestContext(req, respBody.Context); //req.Context = respBody.Context;
            }

            return resp;
        }
    }
}
